/*
日志落地模块:
    功能:将格式化完成后的日志消息字符串, 输出到指定的位置, 并且支持同时将日志落地到不同的位置
    - 标准输出
    - 指定文件
    - 滚动文件 (文件按照时间/大小进行滚动切换)
实现思想:
    - 抽象出落地模块类
    - 不同的落地方向从基类进行派生
    - 使用工厂模式进行 "创建-表示" 分离
*/

#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include "message.hpp"
#include "formatter.hpp"
#include <fstream>
#include <memory>
#include <mutex>
namespace LOG
{
    // 落地类的抽象基类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {}
        // 将长度为len的data日志消息进行日志落地输出
        virtual void log(const char *data, size_t len) = 0;
    };

    // 标准输出
    class StdoutSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<StdoutSink>;
        StdoutSink() = default;
        void log(const char *data, size_t len)
        {
            // 将日志写入标准输出中
            std::cout.write(data, len);
            std:fflush;
        }
    };

    // 文件输出
    class FileSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<FileSink>;
        FileSink(const std::string &filename) : _filename(filename)
        {
            // 创建日志文件所在的目录
            util::File::createDirectory(util::File::path(filename));
            // 打开文件:二进制&&追加
            _ofs.open(_filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }

        const std::string &file() { return _filename; }
        void log(const char *data, size_t len)
        {
            // 将日志消息写入到指定文件中
            _ofs.write((const char *)data, len);
            if (_ofs.good() == false)
            {
                std::cout << "日志输出文件失败!\n";
            }
        }

    private:
        std::string _filename; // 指定输出文件
        std::ofstream _ofs;
    };

    // 滚动文件(文件大小)
    class RollSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<RollSink>;
        // 构造时传入文件名, 并且打开文件
        RollSink(const std::string &basename, size_t max_fsize) : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0), _name_cnt(0)
        {
            std::string filename = createFilename();
            // 创建日志文件所在的目录
            util::File::createDirectory(util::File::path(filename));
            // 打开文件:二进制&&追加
            _ofs.open(filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }

        // 将日志消息写入标准输出--写入前判断文件大小, 超出阈值则切换文件
        void log(const char *data, size_t len)
        {
            if (_cur_fsize >= _max_fsize)
            {
                _ofs.close(); // 先把原来的文件关闭掉
                std::string filename = createFilename();
                _cur_fsize = 0;
                _ofs.open(filename, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }
            _ofs.write(data, len);
            assert(_ofs.good());
            _cur_fsize += len;
        }

    private:
        void initLogFile()
        {
        }
        // 进行文件大小判断, 超出则创建新文件
        std::string createFilename()
        {
            // 获取系统时间: 基础文件名字+扩展文件名
            // 2025/02/02-12:42:52.log
            time_t t = util::Date::now();
            struct tm lt;
            localtime_r(&t, &lt);
            std::stringstream ss;
            ss << _basename;
            ss << lt.tm_year + 1900;
            ss << lt.tm_mon + 1;
            ss << lt.tm_mday;
            ss << "-";
            ss << lt.tm_hour;
            ss << ":";
            ss << lt.tm_min;
            ss << ":";
            ss << lt.tm_sec;
            ss << "-";
            ss << _name_cnt++;
            ss << ".log";
            return ss.str();
        }

    private:
        // 输出文件名=基础文件名+扩展文件名
        int _name_cnt;
        std::string _basename; // 滚动文件的基础名字 ./logs/base-时间戳
        std::ofstream _ofs;    // 对文件进行操作
        size_t _max_fsize;     // 文件大小阈值, 超出则进行切换滚动
        size_t _cur_fsize;     // 记录当前文件大小
    };

    // 抽象工厂模式
    class SinkFactory
    {
    public:
        template <typename SinkType, typename... Args>
        // 设置模板和可变参数是为了更好的支持用户扩展不同的落地方向
        static LogSink::ptr create(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif
